package com.security.sm4;

import cn.hutool.core.util.HexUtil;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Base64;

public class Test1 {
    /*
     * SM4算法名称
     * */
    private static final String ALGORITHM_NAME = "SM4";

    /**
     *  加密模式和填充
     * */
    public static final String CIPHER_ALGORITHM_ECB_WITH_PKCS5PADDING = "SM4/ECB/PKCS5Padding";

    public static final String CIPHER_ALGORITHM_CBC_WITH_PKCS5PADDING = "SM4/CBC/PKCS5Padding";

    public static final String CIPHER_ALGORITHM_ECB_WITH_PKCS7PADDING = "SM4/ECB/PKCS7Padding";

    public static final String CIPHER_ALGORITHM_CBC_WITH_PKCS7PADDING = "SM4/CBC/PKCS7Padding";

    public static final String CIPHER_ALGORITHM_CBC_WITH_NOPADDING = "SM4/CBC/NOPadding";

    public static final String CIPHER_ALGORITHM_ECB_WITH_NOPADDING = "SM4/ECB/NOPadding";
    public static final String CIPHER_ALGORITHM_ECB_WITH_ZEROPADDING = "SM4/ECB/ZEROBYTEPADDING";

    /**
     * 默认密钥长度 只有126bit
     */
    private static final int DEFAULT_KEY_SIZE = 128;

    /**
     * 默认向量 要求16字节
     */
    private static final byte[] DEFAULT_IV = HexUtil.decodeHex("1234567890123456");

    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }
    /**
     * 创建密钥
     *
     *
     * @return base64格式的密钥
     * @throws Exception 创建密钥异常
     */
    public static String generateKey() throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
        keyGenerator.init(DEFAULT_KEY_SIZE, new SecureRandom());
        return Base64.getEncoder().encodeToString(keyGenerator.generateKey().getEncoded());
    }
    /**
     * 加密
     *
     * @param data 要加密的明文
     * @param padding 填充方式
     * @param iv 如果是cbc 则需要iv
     * @return 加密后的密文
     * @throws Exception 加密异常
     */
    public static String encrypt(String data,String key,String padding,String iv) throws Exception {
        return sm4(data, key, Cipher.ENCRYPT_MODE,padding,iv);
    }

    /**
     * 解密
     *
     * @param data 要解密的密文
     * @param padding 填充方式
     * @param iv 如果是cbc 则需要iv
     * @return 解密后的明文
     * @throws Exception 解密异常
     */
    public static String decrypt(String data,String key,String padding,String iv) throws Exception {
        return sm4(data, key , Cipher.DECRYPT_MODE,padding,iv);
    }

    /**
     * 加解密核心方法
     *
     * @param input 明文（加密模式）或密文（解密模式）
     * @param key   密钥
     * @param mode  1-加密 2-解密
     * @param padding 填充方式
     * @param iv 向量 如传则使用传的 不传则使用默认的
     * @return 密文（加密模式）或明文（解密模式）
     * @throws Exception 加解密异常
     */
    private static String sm4(String input,String key, int mode,String padding,String iv)
            throws Exception {
        SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.getDecoder().decode(key), ALGORITHM_NAME);
        Cipher cipher = Cipher
                .getInstance(padding, BouncyCastleProvider.PROVIDER_NAME);
        if (StringUtils.containsIgnoreCase(padding,"CBC")){
            cipher.init(mode, secretKeySpec,new IvParameterSpec(StringUtils.isBlank(iv) ? DEFAULT_IV : Base64.getDecoder().decode(iv)));
        }else {
            cipher.init(mode, secretKeySpec);
        }
        return Base64.getEncoder().encodeToString(cipher.doFinal(Base64.getDecoder().decode(input)));
    }

    /**
     * 测试 判断最后是否输出true
     */
    public static void main(String[] args) throws Exception {
        String s = generateKey();
        String padding = CIPHER_ALGORITHM_CBC_WITH_PKCS7PADDING;
        padding = CIPHER_ALGORITHM_ECB_WITH_ZEROPADDING;
        String data = "123456789123456";
        String iv = Base64.getEncoder().encodeToString("testivtestivtest".getBytes(StandardCharsets.UTF_8));
        String encryptStr = encrypt(Base64.getEncoder().encodeToString(data.getBytes(StandardCharsets.UTF_8)), s, padding, iv);
        System.out.println(encryptStr);
        String decryptStr = decrypt(encryptStr, s, padding, iv);
        System.out.println(new String(Base64.getDecoder().decode(decryptStr)));
        System.out.println(data.equals(new String(Base64.getDecoder().decode(decryptStr))));

    }



}
